package org.jing.core.lang.data;

import org.jing.core.lang.JingException;

import java.util.ArrayList;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;

/**
 * Description: 缓存Map. <br>
 * 键值对在插入时会获取时间戳. <br>
 * 每次对键的操作都会更新其时间戳并将之移动到队列最前端. <br>
 * 超出时间戳或大小限制的键值对会被清除出去. <br>
 *
 *
 * @author bks <br>
 * @since 2024-07-24 <br>
 */
@SuppressWarnings("unused")
public final class CacheMap<K, V> {
    private final ConcurrentHashMap<K, V> map = new ConcurrentHashMap<>();

    private final ArrayList<Pair2<K, Long>> queue = new ArrayList<>();

    /**
     * Description: 数据量, 默认1000 <br>
     * -1时代表不做限制 <br>
     */
    private int size = 1000;

    /**
     * Description: 有效期(毫秒), 默认一天 <br>
     * -1时代表不做限制
     */
    private long time = 1000 * 60 * 60 * 24;

    private final Object LOCK = new Object();

    public CacheMap() {
    }

    /**
     * Description: 初始化一个缓存Map并规定好失效时间. <br>
     *
     * @param size 大小 <br>
     * @param time 失效时间 <br>
     */
    public CacheMap(int size, long time) {
        this.size = size;
        this.time = time;
    }

    /**
     * Description: 规定缓存大小. <br>
     *
     * @param size 大小 <br>
     * @return <br>
     */
    public CacheMap<K, V> setSize(int size) {
        this.size = size;
        return this;
    }

    /**
     * Description: 规定失效时间. <br>
     *
     * @param time 失效时间 <br>
     * @return <br>
     */
    public CacheMap<K, V> setTime(long time) {
        this.time = time;
        return this;
    }

    /**
     * Description: 设置键值. <br>
     *
     * @param key 键 <br>
     * @param value 值 <br>
     * @return <u>链式调用</u> <br>
     */
    public CacheMap<K, V> put(K key, V value) {
        if (null == value) {
            throw new JingException("value is null");
        }
        map.put(key, value);
        updateQueue(key);
        return this;
    }

    /**
     * Description: 根据键获取值. <br>
     *
     * @param key 键 <br>
     * @return <br>
     */
    public V get(K key) {
        updateQueue();
        V v = map.get(key);
        if (null != v) {
            updateQueue(key);
        }
        return v;
    }

    /**
     * Description: 根据键移除值. <br>
     *
     * @param key 键 <br>
     * @return <br>
     */
    public V remove(K key) {
        updateQueue();
        V v = map.remove(key);
        if (null != v) {
            for (int i$ = 0, l$ = queue.size(); i$ < l$; i$ ++) {
                if (queue.get(i$).getA().equals(key)) {
                    queue.remove(i$);
                    break;
                }
            }
        }
        return v;
    }

    /**
     * Description: 是否包含某个Key <br>
     *
     * @param key <br>
     * @return <br>
     */
    public boolean contains(K key) {
        updateQueue();
        boolean flag = map.containsKey(key);
        if (flag) {
            updateQueue(key);
        }
        return flag;
    }

    /**
     * Description: 删掉不符合时间限制的项目 <br>
     */
    private void updateQueue() {
        if (queue.isEmpty()) return;
        synchronized (LOCK) {
            Pair2<K, Long> p;
            // 删除超过长度的
            if (size >= 0) {
                for (int i$ = queue.size() - 1; i$ >= size; i$ --) {
                    p = queue.remove(i$);
                    map.remove(p.getA());
                }
            }
            // 删除超过时间的
            if (time >= 0) {
                long now = new Date().getTime();
                while (!queue.isEmpty()) {
                    p = queue.remove(queue.size() - 1);
                    if (now - p.getB() <= time) {
                        queue.add(p);
                        break;
                    }
                    else {
                        map.remove(p.getA());
                    }
                }
            }
        }
    }

    /**
     * Description: 将对应键的时间戳更新并移到最前面 <br>
     *
     * @param key 键 <br>
     */
    private void updateQueue(K key) {
        synchronized (LOCK) {
            boolean flag = false;
            for (int i$ = 0, l$ = queue.size(); i$ < l$; i$ ++) {
                if (queue.get(i$).getA().equals(key)) {
                    Pair2<K, Long> p = queue.remove(i$);
                    p.setB(new Date().getTime());
                    queue.add(0, p);
                    flag = true;
                    break;
                }
            }
            if (!flag) {
                queue.add(0, new Pair2<>(key, new Date().getTime()));
            }
        }
    }

    /**
     * Description: 清空Map. <br>
     */
    public void clear() {
        synchronized (LOCK) {
            map.clear();
            queue.clear();
        }
    }

    /**
     * Description: 遍历. <br>
     *
     * @param action 方法 <br>
     */
    public void forEach(BiConsumer<? super K, ? super V> action) {
        map.forEach(action);
    }
}